package  AntPheromones;

/**
TorusWorld

Extends GridWorld, a world of discrete cells,
so that moveObject now moves things around a torus.
Also provides x/y-norm methods to return torus-normalized
values for raw x,y values.

*/

import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;

public class TorusWorld extends GridWorld {

	public TorusWorld ( int sizeX, int sizeY, Model aModel ) {
		super( sizeX, sizeY, aModel );
	}


	/**
	 * moveObject
	 * @param ObjectInGrid
	 * @param int dX
	 * @param int dY
	 * @return boolean
	 * try to move object to the requested dx,dy .
	 * note we must check to be sure the new location is "legal", i.e., cell is empty 
	 * If move is ok:  move in world, and tell object its new x,y, and return true
	 * if not ok, return false
	 */
	public boolean moveObject ( ObjectInGrid obj, int dX, int dY ) {
		int currentX = obj.getX();	// where it is
		int currentY = obj.getY();

		int newX = xnorm( obj.getX() + dX );	// get location it wants to move to
		int newY = ynorm( obj.getY() + dY );

		// see if new cell is empty.
		if ( getObjectAt( newX, newY ) != null ) {
			return false;
		}

		// its ok to move, so tell the world and the object about the move
		putObjectAt( currentX, currentY, null );	// old cell empty now
		putObjectAt( newX, newY, obj );			   	// obj in new cell now
		obj.setX( newX );							// tell object its new location
		obj.setY( newY );
		return true;
	}

	/**
	 * getOpenNeighborLocations
	 * @param int x
	 * @param int y 
	 * @return a ArrayList of Points, one point for each unoccupied cell in the 
	 * Moore neighborhood around the given x,y location.
	 * Notes: includes point for x,y if its open.
	 *       returns torus normalized x,y values in the Points.
	 */

	public ArrayList<Point> getOpenNeighborLocations ( int x, int y ) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		
		// figure out the range of cells to search
		int minX = x - 1;
		int maxX = x + 1;
		int minY = y - 1;
		int maxY = y + 1;

		// search area for open cells
		for ( int ty = minY; ty <= maxY; ++ty ) {
			for ( int tx = minX; tx <= maxX; ++tx ) {
				int txnorm = xnorm( tx );				// normalize for torus
				int tynorm = ynorm( ty );
				if ( getObjectAt( txnorm, tynorm ) == null ) { // its open
					Point p = new Point( txnorm, tynorm );
					ptList.add( p );
				}
			}
		}

		return ptList;
	}

	/**
	 * xnorm
	 * return torus-normalized values for raw x value.
	 * @param x
	 * @return int 
	 */
	public int xnorm ( int x ) { 
		if ( x > xSize -1 || x < 0 ) {
			while ( x < 0 ) x += xSize;
			return x % xSize; 
		}
		return x;
	}
	/**
	 * ynorm
	 * return torus-normalized values for raw y value.
	 * @param y
	 * @return int 
	 */
	public int ynorm ( int y ) { 
		if ( y > ySize -1 || y < 0 ) {
			while ( y < 0 ) y += ySize;
			return y % ySize; 
		}
		return y;
	}
	
	/**
	 This function determines whether a coordinate is an open location on the torus world
	 */
	public boolean isOpenLocation (int x, int y) {
		if (getObjectAt(xnorm(x), ynorm(y)) == null)
			return true;
		else return false;				
	}
	
	/**
	 * This function search for open locations starting from a center coordinate
	 * and go out to larger radius until the specified number of locations are found.
	 * @param numObject
	 * @param centerX
	 * @param centerY
	 * @return ArrayList<Point> the list of open locations
	 * 
	 */
	public ArrayList<Point> findLocationForPiledObjectsSquare (int numObject, int centerX, int centerY) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		int objectCount = 0;
		int maxX = centerX, maxY = centerY, minX = centerX, minY = centerY;
		while (objectCount < numObject) {
			
			//stop if the range is larger than torus size
			if (maxX - minX > xSize || maxY - minY > ySize) break;
			
			//label for easier breaking the searching loop:
			searchForOpenLocations:
			for (int i = minX; i < maxX + 1; i++) {
				for (int j = minY; j < maxY + 1; j++) {
					if (isOpenLocation(i, j)) {
						Point p = new Point(xnorm(i), ynorm(j));
						if (!ptList.contains(p)) {
							ptList.add(p);
							++objectCount;
						}
						if (objectCount == numObject){
							break searchForOpenLocations;
						}
					}
				}
			}
			--minX; ++maxX; --minY; ++maxY;
		}
		if (objectCount < numObject) {
			System.out.printf( "\n** World too full (%d) for this pile!\n\n", numObject);
		}
		return ptList;
	}
	
	/**
	 * This function scan the entire grid and return the list of locations whose distance to
	 * the center is less than the given radius and is not yet occupied
	 * @param radius
	 * @param centerX
	 * @param centerY
	 * @return
	 */
	public ArrayList<Point> findLocationForPiledObjectsRound (double radius, int centerX, int centerY) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		for (int i = 0; i < xSize; i ++) {
			for (int j = 0; j < ySize; j++) {
				if (torusDistance(centerX, centerY, i, j) < radius) {
					if (isOpenLocation(i, j)) {
						Point p = new Point(xnorm(i), ynorm(j));
						ptList.add(p);
					}
				}
			}
		}
		return ptList;
	}
	/**
	 * Used with calculated distance map.
	 * return a list of points with smaller distance to origin.
	 * @param x
	 * @param y
	 * @return
	 */
	public ArrayList<Point> findMooreNeighborsWithSmallerDistance (int x, int y) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		for (int i = x - 1; i <= x + 1; i++) {
			for (int j = y - 1; j <= y + 1; j++){
				if (getValueAt(xnorm(i),ynorm(j)) < getValueAt(x, y)) {
					Point p = new Point(xnorm(i),ynorm(j));
					ptList.add(p);
				}
			}
		}
		return ptList;
	}
	/**
	 * takes in a point list, and remove the points in this list that are occupied positions
	 * @param ptList
	 */
	public void removeOccupied (ArrayList<Point> ptList) {
		Iterator<Point> ptIter = ptList.iterator();
		while ( ptIter.hasNext() ) {
			Point p = ptIter.next();
			if ( getObjectAt((int)(p.getX()), (int)(p.getY())) != null ) { 
				ptIter.remove();  // remove it from list
			}
		}
	}
	/**
	 * this function returns the distance between two points on the torus world.
	 * @param oX x coordinate of origin
	 * @param oY y coordinate of origin
	 * @param x x coordinate of destination
	 * @param y y coordinate of destination
	 * @return
	 */
	public double torusDistance (int oX, int oY, int x, int y) {
		int dX = Math.abs(oX - x);
		if (dX > xSize/2) dX = xSize - dX;
		int dY = Math.abs(oY - y);
		if (dY > ySize/2) dY = ySize - dY;
		double distance = Math.sqrt((double)(dX * dX + dY * dY));
		return distance;
	}
	
	/**
	 * calculate the distance between each location and origin.
	 * value stored in TorusWorld.
	 * @param oX
	 * @param oY
	 */
	public void calcDistanceToOrigin (int oX, int oY) {
		double distance;
		for (int i = 0; i < xSize; i ++) {
			for (int j = 0; j < ySize; j++) {
				distance = torusDistance(oX, oY, i, j);
				putValueAt(i, j, distance);
			}
		}
	}
	
	/**
	 * 
	 * Calculate points that are closer to a given to point than a given from point
	 * @param fromx x cor of point you are currently at
	 * @param fromy y cor of point you are currently at
	 * @param tox x cor of point you want to get closer to
	 * @param toy y cor of point you want to get closer to
	 * @return Points that are closer
	 */
	public ArrayList<Point> findNeighborsWithSmallerDistanceToPoint (int fromx, int fromy, int tox, int toy) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		double fromDistance = torusDistance(fromx, fromy, tox, toy); //calculate distance to compare against
		for (int i = fromx - 1; i <= fromx + 1; i++) {
			for (int j = fromy - 1; j <= fromy + 1; j++){
				double tempDistance = torusDistance(xnorm(i), ynorm(j), tox, toy); //calculate current distance of point
				if (tempDistance < fromDistance) {
					Point p = new Point(xnorm(i),ynorm(j));
					ptList.add(p);
				}
			}
		}
		return ptList;
	}
}

